from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog
from main_ui import Ui_MainWindow   # 导入生成的界面类
import os, time
from PySide6.QtCore import QThread, Signal
import build_action
import build_log_analyse
from queue import Queue

# The build_cmd_result_queue is used to store the result of the build command
# send the result to the build_log_analyse_thread to determine whether the build is successful
# or failed, and then the build log analyse thread will stop the build log analyse process
build_cmd_result_queue = Queue()
flash_cmd_result_queue = Queue()

# The line_set is used to store the lines of the build log file
# to avoid the same line being printed multiple times
line_set = set()

# The build_action_thread is used to run the build command

class build_action_thread(QThread):
    bar_val_update_signal = Signal(str, int)
    build_cmd_result_signal = Signal(str)
    def __init__(self, parent=None):
        super().__init__(parent)

    def run(self):
        print("Build started")

        # Remove the build.log file if it exists
        # to avoid the build log file being appended
        if os.path.exists("build.log"):
            os.remove("build.log")
            
        # The cmd_file is the command file to build the project
        # The run_cmd function is used to run the command file
        # Start the build process
        cmd_file = 'build.cmd'
        ret = build_action.run_cmd(cmd_file)

        # Send the result of the build command to the build log analyse thread
        # to determine whether the build is successful or failed and stop the build log analyse process
        if ret == True:
            self.build_cmd_result_signal.emit("True")
            # build_cmd_result_queue.put("True")
        else:
            self.build_cmd_result_signal.emit("False")
            build_cmd_result_queue.put("False")

class flash_log_analyse_thread(QThread):
    
    log_print_signal = Signal(str, str)
    bar_val_update_signal = Signal(str, int)
    flash_exit_signal = Signal(str)
    bar_error_signal = Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.bar_all_clear()
        line_set.clear()
        self.flash_log_path = "flash.log"
        self.flash_exit_signal.emit("False")

    def run(self):
        print("Flash log analyse started")
        self.bar_all_clear()
        line_set.clear()

        attempt_open_max = 5

        while(1):
            if flash_cmd_result_queue.empty() == False:
                if flash_cmd_result_queue.get() == "True":
                    return
                else:
                    self.error_in_bar(self.analyse_status)
                    return

            if os.path.exists(self.flash_log_path) == False:
                continue

            for attempt in range(attempt_open_max):
                try:
                    if self.log_analyse(self.flash_log_path):
                        print("Flash log analyse finished")
                        return
                except Exception as e:
                    print("Attempt open flash log file failed: ", attempt)
                    time.sleep(0.1)

            time.sleep(0.1)

    def log_analyse(self, path):
        with open(path, 'r') as file:
            for line in file:
                if line not in line_set:
                    line_set.add(line)
                    self.log_print_signal.emit("all", line)

                    if build_log_analyse.analyse_is_finished(line):
                        print("Flash log analyse finished")
                        file.close()
                        return True

    def error_in_bar(self, bar):
        self.bar_val_update_signal.emit(bar)
        
    def bar_all_clear(self):
        self.bar_val_update_signal.emit("erase_file_bar", 0)
        self.bar_val_update_signal.emit("program_file_bar", 0)
        self.bar_val_update_signal.emit("verify_file_bar", 0)



# The build_log_analyse_thread is used to analyse the build log file
# and update the value of the progress bar

class build_log_analyse_thread(QThread):

    # The bar_val_update_signal is used to update the value of the progress bar
    bar_val_update_signal = Signal(str, int)
    # The build_exit_signal is used to indicate whether the build is finished
    build_exit_signal = Signal(str)
    # The bar_error_signal is used to indicate the error in the progress
    bar_error_signal = Signal(str)

    # The log_print_signal is used to print the log to the log editor
    log_print_signal = Signal(str, str)


    def __init__(self, parent=None):
        super().__init__(parent)

        # The analyse_status is used to indicate current status of the build process
        self.analyse_status = "generate_build_system_bar"
        # reset the progress bar
        self.bar_all_clear()
        self.build_log_path = "build.log"
        # The line_set is used to store the lines of the build log file
        # to avoid the same line being printed multiple times
        line_set.clear()
        self.build_exit_signal.emit("False")

    # The log_analyse function is used to analyse the build log file

    def log_analyse(self, path):
        with open(path, 'r') as file:
            for line in file:
                # If the line is not in the line_set, print the line to the log editor,  and analyse the line
                if line not in line_set:
                    line_set.add(line)
                    if build_log_analyse.analyse_is_warning(line):
                        self.log_print_signal.emit("warring", line)
                        line = ''' <p style="background-color: yellow; color: black;">
                                ''' + line + '''</p> '''
                    self.log_print_signal.emit("all", line)

                    # If the analyse is finished, stop the analyse process
                    if build_log_analyse.analyse_is_finished(line):
                        print("Build log analyse finished")
                        file.close()

                    if self.line_analyse(line):
                        return True
                

        return False

    # The line_analyse function is used to analyse the line of the build log file
    # and update the value of the progress bar

    def line_analyse(self, line):
        if self.analyse_status == "generate_build_system_bar":
            bar, val = build_log_analyse.analyse_in_generate_build_system(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.analyse_status = "cmake_config_bar"
        elif self.analyse_status == "cmake_config_bar":
            bar, val = build_log_analyse.analyse_in_cmake_config(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.analyse_status = "devicetree_config_bar"
        elif self.analyse_status == "devicetree_config_bar":
            bar, val = build_log_analyse.analyse_in_devicetree_config(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.analyse_status = "kconfig_process_bar"
        elif self.analyse_status == "kconfig_process_bar":
            bar, val = build_log_analyse.analyse_in_kconfig_process(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.analyse_status = "compilers_recognize_bar"
        elif self.analyse_status == "compilers_recognize_bar":
            bar, val = build_log_analyse.analyse_in_compilers_recognize(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.analyse_status = "buildapplication_bar"
        elif self.analyse_status == "buildapplication_bar":
            bar, val = build_log_analyse.analyse_in_buildapplication(line)
            self.bar_value_update(bar, val)
            if val == 100:
                self.build_exit_signal.emit("True")
                return True
        return False

    # The error_in_bar function is used to indicate the error in the progress
    def error_in_bar(self, bar):
        self.bar_error_signal.emit(bar)

    def run(self):
        print("Build log analyse started")

        # Reset the progress bar and the line_set
        self.bar_all_clear()
        line_set.clear()

        # The attempt_open_max is the maximum number of attempts to open the build log file
        # Sometimes the build log file is not created immediately after the build process starts
        # So we need to wait for a while and try to open the build log file

        attempt_open_max = 5

        while(1):

            # here we need to check the build_cmd_result_queue
            # if the build_cmd_result_queue is not empty, we need to get the result of the build command
            # if the result is "True", it means the build is successful, we can stop the build log analyse process
            # if the result is "False", it means the build is failed, we need to indicate the error in the progress

            if build_cmd_result_queue.empty() == False:
                if build_cmd_result_queue.get() == "True":
                    return
                else:
                    self.error_in_bar(self.analyse_status)
                    return

            if os.path.exists(self.build_log_path) == False:
                continue

            for attempt in range(attempt_open_max):
                try:

                    # If the build log file is opened successfully, we can start the build log analyse process

                    if self.log_analyse(self.build_log_path):
                        print("Build log analyse finished")
                        return
                            
                except Exception as e:
                    print("Attempt open build log file failed: ", attempt)
                    time.sleep(0.1)

            time.sleep(0.1) 

    def bar_all_clear(self):
        self.bar_val_update_signal.emit("generate_build_system_bar", 0)
        self.bar_val_update_signal.emit("cmake_config_bar", 0)
        self.bar_val_update_signal.emit("compilers_recognize_bar", 0)
        self.bar_val_update_signal.emit("kconfig_process_bar", 0)
        self.bar_val_update_signal.emit("buildapplication_bar", 0)
        self.bar_val_update_signal.emit("devicetree_config_bar", 0)

    def bar_value_update(self, bar, val):
        if bar == "generate_build_system_bar":
            self.bar_val_update_signal.emit(bar, val)
        elif bar == "cmake_config_bar":
            self.bar_val_update_signal.emit(bar, val)
        elif bar == "compilers_recognize_bar":
            self.bar_val_update_signal.emit(bar, val)
        elif bar == "kconfig_process_bar":
            self.bar_val_update_signal.emit(bar, val)
        elif bar == "buildapplication_bar":
            self.bar_val_update_signal.emit(bar, val)
        elif bar == "devicetree_config_bar":
            self.bar_val_update_signal.emit(bar, val)

class error_analyse_thread(QThread):

    # The log_print_signal is used to print the log to the log editor
    log_print_signal = Signal(str, str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.build_log_path = "build.log"

    def run(self):
        print("Error analyse started")
        if os.path.exists(self.build_log_path) == False:
                time.sleep(1)
        time.sleep(1)
        self.check_miss_log(self.build_log_path)
        self.warning_analyse(self.build_log_path)
        self.error_analyse(self.build_log_path)

    # The check_miss_log function is used to check the missing log
    # Sometimes the build log analyse thread broke down when the error occurred in the build process
    # and the log is not printed to the log editor

    def check_miss_log(self, path):
        if os.path.exists(path) == False:
            return False
        with open(path, 'r') as file:
            for line in file:
                if line in line_set:
                    continue
                line_set.add(line)

                # If the line is an error, print the line to the log editor and set the background color to red
                if build_log_analyse.analyse_is_error(line):
                    line = ''' <p style="background-color: red; color: white;">
                            ''' + line + '''</p> '''
                self.log_print_signal.emit("all", line)
        return True

    # The error_analyse function is used to analyse the error in the build log file
    # and print the error to the error log tab

    def error_analyse(self, path):
        if os.path.exists(path) == False:
            return False
        with open(path, 'r') as file:
            for line in file:
                if build_log_analyse.analyse_is_error(line):
                    self.log_print_signal.emit("error", line)

        return False
    
    def warning_analyse(self, path):
        with open(path, 'r') as file:
            for line in file:
                if build_log_analyse.analyse_is_warning(line):
                    self.log_print_signal.emit("warring", line)

class memory_analyse_thread(QThread):
    log_print_signal = Signal(str, str)
    used_memory_signal = Signal(str, int)
    def __init__(self, parent=None):
        super().__init__(parent)
        self.build_log_path = "build.log"

    def run(self):
        print("Memory analyse started")
        self.check_miss_log(self.build_log_path)
        self.memory_analyse(self.build_log_path)

    def check_miss_log(self, path):
        if os.path.exists(path) == False:
            return False
        with open(path, 'r') as file:
            for line in file:
                if line in line_set:
                    continue
                line_set.add(line)

                # If the line is an error, print the line to the log editor and set the background color to red

                if build_log_analyse.analyse_is_error(line):
                    line = ''' <p style="background-color: red; color: white;">
                            ''' + line + '''</p> '''
                self.log_print_signal.emit("all", line)
        return True
    
    def memory_analyse(self, path):
        if os.path.exists(path) == False:
            return False
        with open(path, 'r') as file:
            for line in file:
                bar, val = build_log_analyse.analyse_mem_usage(line)
                if bar != None:
                    self.used_memory_signal.emit(bar, val)
        return True

class analyse_key_log(QThread):
    log_print_signal = Signal(str, str)
    def __init__(self, parent=None):
        super().__init__(parent)
        self.build_log_path = "build.log"

    def run(self):
        print("Analyse key log started")
        self.analyse(self.build_log_path)

    def analyse(self, path):
        if os.path.exists(path) == False:
            return False
        with open(path, 'r') as file:
            for line in file:
                if build_log_analyse.analyse_is_key_error(line):
                    self.log_print_signal.emit("key_log", line)
        return True

class flash_action_thread(QThread):
    bar_val_update_signal = Signal(str, int)
    flash_cmd_result_signal = Signal(str)
    def __init__(self, parent=None):
        super().__init__(parent)

    def run(self):
        print("flash started")

        # Remove the build.log file if it exists
        # to avoid the build log file being appended
        if os.path.exists("flash.log"):
            os.remove("flash.log")
            
        # The cmd_file is the command file to build the project
        # The run_cmd function is used to run the command file
        # Start the build process
        cmd_file = 'flash.cmd'
        ret = build_action.run_cmd(cmd_file)

        # Send the result of the build command to the build log analyse thread
        # to determine whether the build is successful or failed and stop the build log analyse process
        if ret == True:
            self.flash_cmd_result_signal.emit("True")
            # build_cmd_result_queue.put("True")
        else:
            self.flash_cmd_result_signal.emit("False")
            flash_cmd_result_queue.put("False")

class MainWindow(QMainWindow):
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")
        self.ui = Ui_MainWindow()   # 创建界面类实例
        self.ui.setupUi(self)       # 构建界面
        self.ui_init()              # 初始化界面

    def bar_init(self, bar):
        bar.setStyleSheet("QProgressBar::chunk {background-color: green;}"
                                             "QProgressBar{text-align: center;}")
        bar.setValue(0)


    ## Set the color of the progress bar according to the value
    ## val = 100: green means success
    ## val > 0 and val < 100: yellow means processing
    ## val < 0: red means error
        
    def bar_background_color_set(self, bar, val):
        if val == 100:
            bar.setStyleSheet("QProgressBar::chunk {background-color: green;}"
                                             "QProgressBar{text-align: center;}")
        elif val > 0 and val < 100:
            bar.setStyleSheet("QProgressBar::chunk {background-color: yellow;}"
                                             "QProgressBar{text-align: center;}")
        else:
            bar.setStyleSheet("QProgressBar::chunk {background-color: red;}"
                                             "QProgressBar{text-align: center;}")

    ## Update the value of the progress bar
    ## bar: the name of the progress bar
    ## val: the value of the progress bar
    ## Use to indicate the progress of the build process

    def bar_val_update(self, bar, val):
        if bar == "generate_build_system_bar":
            self.ui.generate_build_system_bar.setValue(val)
            self.bar_background_color_set(self.ui.generate_build_system_bar, val)
        elif bar == "cmake_config_bar":
            self.ui.cmake_config_bar.setValue(val)
            self.bar_background_color_set(self.ui.cmake_config_bar, val)
        elif bar == "compilers_recognize_bar":
            self.ui.compilers_recognize_bar.setValue(val)
            self.bar_background_color_set(self.ui.compilers_recognize_bar, val)
        elif bar == "kconfig_process_bar":
            self.ui.kconfig_process_bar.setValue(val)
            self.bar_background_color_set(self.ui.kconfig_process_bar, val)
        elif bar == "buildapplication_bar":
            self.ui.buildapplication_bar.setValue(val)
            self.bar_background_color_set(self.ui.buildapplication_bar, val)
        elif bar == "devicetree_config_bar":
            self.ui.devicetree_config_bar.setValue(val)
            self.bar_background_color_set(self.ui.devicetree_config_bar, val)
        elif bar == "erase_file_bar":
            self.ui.erase_file_bar.setValue(val)
            self.bar_background_color_set(self.ui.erase_file_bar, val)
        elif bar == "program_file_bar":
            self.ui.program_file_bar.setValue(val)
            self.bar_background_color_set(self.ui.program_file_bar, val)
        elif bar == "verify_file_bar":
            self.ui.verify_file_bar.setValue(val)
            self.bar_background_color_set(self.ui.verify_file_bar, val)
    
    ## Set the text of the progress bar
    def bar_set_text(self, bar, text):
        bar.setFormat(text)


    ## Initialize the progress bar of the build process

    def build_bar_init(self):
        self.bar_init(self.ui.generate_build_system_bar)
        self.bar_init(self.ui.cmake_config_bar)
        self.bar_init(self.ui.compilers_recognize_bar)
        self.bar_init(self.ui.kconfig_process_bar)
        self.bar_init(self.ui.buildapplication_bar)
        self.bar_init(self.ui.devicetree_config_bar)

    ## Initialize the progress bar of the flash process

    def flash_bar_init(self):
        self.bar_init(self.ui.erase_file_bar)
        self.bar_init(self.ui.program_file_bar)
        self.bar_init(self.ui.verify_file_bar)

    ## Initialize the UI and connect the signals and slots when the UI is created

    def ui_init(self):
        self.build_bar_init()
        self.flash_bar_init()
        self.mem_use_bar_clear()

        self.ui.toolchain_path_bp.clicked.connect(self.toolchain_path_bp_clicked)
        self.ui.project_bp.clicked.connect(self.project_bp_clicked)
        self.ui.sdk_path_bp.clicked.connect(self.sdk_path_bp_clicked)
        self.ui.exit_bp.clicked.connect(self.exit_bp_clicked)
        self.ui.build_pd.clicked.connect(self.build_pd_clicked)
        self.ui.flash_dp.clicked.connect(self.flash_dp_clicked)
        self.ui.copy_pb.clicked.connect(self.copy_pb_clicked)
        self.ui.analyse_pb.clicked.connect(self.analyse_pb_clicked)

        # set the initial log tag to "all"
        self.log_tag_current_index_changed("all")

    def mem_use_bar_clear(self):
        self.ui.flash_use_bar.setStyleSheet("QProgressBar::chunk {background-color: green;}"
                                             "QProgressBar{text-align: center;}")
        self.ui.ram_use_bar.setStyleSheet("QProgressBar::chunk {background-color: green;}"
                                            "QProgressBar{text-align: center;}")
        self.ui.idt_use_bar.setStyleSheet("QProgressBar::chunk {background-color: green;}"
                                            "QProgressBar{text-align: center;}")
        self.ui.flash_use_bar.setValue(0)
        self.ui.ram_use_bar.setValue(0)
        self.ui.idt_use_bar.setValue(0)

    def mem_use_bar_update(self, bar, val):
        if bar == "flash_use_bar":
            self.ui.flash_use_bar.setValue(val)
        elif bar == "ram_use_bar":
            self.ui.ram_use_bar.setValue(val)
        elif bar == "idt_use_bar":
            self.ui.idt_use_bar.setValue(val)

    def log_tag_current_index_changed(self, index):
        if index == "all":
            self.ui.log_tag.setCurrentIndex(0)
        elif index == "warring":
            self.ui.log_tag.setCurrentIndex(1)
        elif index == "error":
            self.ui.log_tag.setCurrentIndex(2)
        elif index == "key_log":
            self.ui.log_tag.setCurrentIndex(3)

    def log_ed_append(self, index, text):
        if index == "all":
            self.ui.log_all_ed.append(text)
            # self.log_tag_current_index_changed("all")
        elif index == "warring":
            self.ui.log_warring_ed.append(text)
            self.log_tag_current_index_changed("warring")
        elif index == "error":
            print("error:" + text)
            self.ui.log_error_ed.append(text)
            self.log_tag_current_index_changed("error")
        elif index == "key_log":
            self.ui.log_key_ed.append(text)
            self.log_tag_current_index_changed("key_log")

    def analyse_pb_clicked(self):
        print("Analyse button clicked")
        self.analyse_key_log_thread = analyse_key_log()
        self.analyse_key_log_thread.start()
        self.analyse_key_log_thread.log_print_signal.connect(self.log_ed_append)

    def log_ed_all_clear(self):
        self.ui.log_all_ed.clear()
        self.ui.log_warring_ed.clear() 
        self.ui.log_error_ed.clear()
        self.ui.log_key_ed.clear()

    def copy_pb_clicked(self):
        print("Copy button clicked")
        if self.ui.log_tag.currentIndex() == 0:
            self.ui.log_all_ed.selectAll()
            self.ui.log_all_ed.copy()
        elif self.ui.log_tag.currentIndex() == 1:
            self.ui.log_warring_ed.selectAll()
            self.ui.log_warring_ed.copy()
        elif self.ui.log_tag.currentIndex() == 2:
            self.ui.log_error_ed.selectAll()
            self.ui.log_error_ed.copy()

    def toolchain_path_bp_clicked(self):
        self.toolchain_path = QFileDialog.getExistingDirectory(self, "Select Directory", "C:/")
        print("Toolchain path: ", self.toolchain_path)

    def project_bp_clicked(self):
        self.project_path = QFileDialog.getExistingDirectory(self, "Select Directory", "C:/")
        print("Project path: ", self.project_path)

    def sdk_path_bp_clicked(self):
        self.sdk_path = QFileDialog.getExistingDirectory(self, "Select Directory", "C:/")
        print("SDK path: ", self.sdk_path)
    
    def exit_bp_clicked(self):
        QMainWindow.close(self)

    def bar_error_enable(self, bar):
        self.bar_val_update(bar, -1)
        self.build_pd_set_enable("True")
        self.error_analyse_thread = error_analyse_thread()
        self.error_analyse_thread.start()
        self.error_analyse_thread.log_print_signal.connect(self.log_ed_append)

    def build_pd_set_enable(self, enable):
        if enable == "True":
            enable = False
        else:
            enable = True
        self.ui.build_pd.setDisabled(enable)

    def build_exit(self, enable):
        self.build_pd_set_enable(enable)
        self.mem_analyse_thread = memory_analyse_thread()
        self.mem_analyse_thread.start()
        self.mem_analyse_thread.log_print_signal.connect(self.log_ed_append)
        self.mem_analyse_thread.used_memory_signal.connect(self.mem_use_bar_update)
            

    def build_pd_clicked(self):
        self.mem_use_bar_clear()
        self.log_ed_all_clear()
        self.ui.build_pd.setDisabled(True)
        self.build_thread = build_action_thread()
        self.build_thread.start()
        self.build_thread.bar_val_update_signal.connect(self.bar_val_update)
        self.build_log_analyse_thread = build_log_analyse_thread()
        self.build_log_analyse_thread.start()
        self.build_log_analyse_thread.bar_val_update_signal.connect(self.bar_val_update)
        self.build_log_analyse_thread.build_exit_signal.connect(self.build_exit)
        self.build_log_analyse_thread.bar_error_signal.connect(self.bar_error_enable)
        self.build_log_analyse_thread.log_print_signal.connect(self.log_ed_append)

    def flash_dp_clicked(self):
        self.flash_thread = flash_action_thread()
        self.flash_thread.start()
        # self.flash_thread.bar_val_update_signal.connect(self.bar_val_update)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()